import { z } from "zod";
import prisma from "../prisma_client";
import { BaseModel, TableDataProps } from "../utils/vo";
import { zRole } from "./role";
import { Permission, Prisma } from "@prisma/client";
import { convertNullToUndefined } from "~/utils";
import { createHash } from "crypto";
import { md5 } from "../utils/security";
import { buildPermissionTree, zPermission } from "./permission";

export const zUser = BaseModel.extend({
  username: z.string(),
  password: z.string().optional(),
  nickname: z.string().optional(),
  email: z.string().email().optional(),
  mobile: z.string().optional(),
  avatar: z.string().optional(),
  roles: z.array(z.lazy(() => zRole)).optional(),
  permissionPaths: z.array(z.string()).optional(),
  permissions: z.array(zPermission).optional(),
});
export type UserModel = z.infer<typeof zUser>;

export async function findUsers(props: TableDataProps) {
  const skip = props.currentPage && props.pageSize ? (props.currentPage - 1) * props.pageSize : 0;
  const take = props.pageSize ? props.pageSize : undefined;

  const users = await prisma.user.findMany({
    skip,
    take,
    where: {
      OR: [
        {
          username: {
            contains: props.search || "",
          },
        },
        {
          nickname: {
            contains: props.search || "",
          },
        },
        {
          email: {
            contains: props.search || "",
          },
        },
        {
          mobile: {
            contains: props.search || "",
          },
        }
      ],
    },
    include: {
      roles: {
        include: {
          role: true,
        },
      }
    },
    orderBy: {
      [props.column ?? "id"]: props.order ?? "asc",
    },
  });

  return users.map(user => {
    return {
      ...convertNullToUndefined(user),
      roles: user.roles.map(role => {
        return {
          ...convertNullToUndefined(role),
        };
      }),
    };
  });
}

export async function countUsers(props: TableDataProps) {
  return await prisma.user.count({
    where: {
      OR: [
        {
          username: {
            contains: props.search || "",
          },
        },
        {
          nickname: {
            contains: props.search || "",
          },
        },
        {
          email: {
            contains: props.search || "",
          },
        },
        {
          mobile: {
            contains: props.search || "",
          },
        }
      ],
    },
  });
}

export async function getUserByUsernameAndPassword(username: string, password: string) {
  const user = await prisma.user.findUnique({
    where: {
      username: username,
      password: md5(password),
    },
    include: {
      roles: {
        include: {
          role: {
            include: {
              permissions: {
                include: {
                  permission: true
                }
              },
            },
          },
        },
      },
    },
  });
  if (!user) {
    return null;
  }
  const permissions = new Set<Permission>();
  const permissionPaths = new Set<string>();
  user.roles?.forEach(role => {
    role.role.permissions.forEach(rolePermission => {
      const permission = rolePermission.permission;
      if (permission.type === 1) {
        permissions.add(rolePermission.permission);
      }
      if (permission.path) {
        permissionPaths.add(permission.path);
      }
    });
  });
  return {
    ...convertNullToUndefined(user),
    username: user.username,
    permissionPaths: Array.from(permissionPaths),
    permissions: buildPermissionTree(Array.from(permissions)),
  }
}

// function toUserModel(user: Prisma.UserGetPayload<{
//   include: {
//     roles: {
//       include: {
//         role: true,
//       };
//     };
//   };
// }>): UserModel {
//   return {
//     ...convertNullToUndefined(user),
//     roles: user.roles.map(role => {
//       return {
//         ...convertNullToUndefined(role),
//       }
//     })
//   }
// }

export async function createUser(user: UserModel) {
  const hasSameName = await hasSameUsername(undefined, user.username)
  if (hasSameName) {
    return 1;
  }

  const roles: Prisma.UserRoleUncheckedCreateWithoutUserInput[] = [];
  user.roles?.forEach(role => {
    roles.push({
      role_id: role.id!,
      assigned_at: new Date(),
    });
  });
  const password = user.password ? createHash('md5').update(user.password).digest('hex') : undefined;
  return await prisma.user.create({
    data: {
      ...user,
      password: password,
      id: undefined,
      status: 1,
      created_at: new Date(),
      roles: {
        create: roles
      }
    }
  });
}

async function hasSameUsername(id: number | undefined, username: string): Promise<boolean> {
  let userWhere: Prisma.UserWhereInput;
  if (id) {
    userWhere = {
      id: { not: id },
      username: username
    }
  } else {
    userWhere = {
      username: username
    }
  }
  const existUser = await prisma.user.findFirst({
    where: userWhere
  });
  return existUser !== null;
}

export async function updateUser(user: UserModel) {
  const hasSameName = await hasSameUsername(user.id, user.username)
  if (hasSameName) {
    return 1;
  }

  const roles: Prisma.UserRoleUncheckedCreateWithoutUserInput[] = [];
  user.roles?.forEach(role => {
    roles.push({
      role_id: role.id!,
      assigned_at: new Date(),
    })
  });

  return await prisma.user.update({
    where: {
      id: user.id!
    },
    data: {
      ...user,
      password: undefined,
      updated_at: new Date(),
      roles: {
        deleteMany: {},
        create: roles
      }
    }
  });
}

export async function deleteUser(id: number) {
  await prisma.userRole.deleteMany({
    where: {
      user_id: id
    }
  });
  return await prisma.user.delete({
    where: {
      id: id
    }
  });
}